package be.dch.objectprops.parser;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import be.dch.objectprops.ParserFinder;
import be.dch.objectprops.collector.Collector;
import be.dch.objectprops.formatter.Formatter;
import be.dch.util.StringUtils;

import com.google.inject.Inject;

public final class DefaultObjectParser implements Parser {

    private static final Logger LOG = Logger.getLogger(CollectionParser.class);
    @Inject
    private transient Collector props;

    private final transient Map<Class<?>, Formatter> formatterMap = new HashMap<Class<?>, Formatter>();

    @Inject
    private transient ParserFinder finder;
    private static final String PROP_SEPA = ".";

    @Inject
    public DefaultObjectParser(final Set<Formatter> formatterList) {
	for (final Formatter formatter : formatterList) {
	    this.addFormatter(formatter);
	}
    }

    public void addFormatter(final Formatter format) {
	this.formatterMap.put(format.getFormatterClass(), format);
    }

    
    public boolean handleAsWriter(final Object result) {
	return true;
    }

    
    public boolean handleAsReader(final Field field) {
	DefaultObjectParser.LOG.debug("Investigating of object can be handled");
	return true;
    }

    
    public void write(final Object result, final StringBuilder key) {
	DefaultObjectParser.LOG.debug("Writing object:" + result.getClass().getSimpleName());
	final String prefix = key.toString();
	if ((key.toString().length() != 0) && !StringUtils.endsWith(key.toString(), DefaultObjectParser.PROP_SEPA, false)) {
	    key.append(DefaultObjectParser.PROP_SEPA);
	}
	final Field[] fields = result.getClass().getDeclaredFields();
	for (final Field field : fields) {
	    if (this.ignoreLoad(field)) {
		DefaultObjectParser.LOG.debug("Ignoring field " + field.getName() + " in  object" + result.getClass().getSimpleName());
	    } else {
		DefaultObjectParser.LOG.debug("Analysing field :" + field.getName());
		key.setLength(prefix.length());
		if (!prefix.equalsIgnoreCase(prefix + DefaultObjectParser.PROP_SEPA)) {
		    key.append(result.getClass().getSimpleName());
		    key.append(DefaultObjectParser.PROP_SEPA);
		}
		Object resultObject = null;
		try {
		    field.setAccessible(true);
		    key.append(field.getName());
		    resultObject = field.get(result);
		    if (result != null) {
			DefaultObjectParser.LOG.debug("Analysing result:" + result.getClass());
			this.finder.write(resultObject, key);
		    }
		} catch (final IllegalArgumentException e) {
		    DefaultObjectParser.LOG.error(e);
		} catch (final IllegalAccessException e) {
		    DefaultObjectParser.LOG.error(e);
		} catch (final SecurityException e) {
		    DefaultObjectParser.LOG.error(e);
		}
	    }
	}

    }

    
    public Object read(final StringBuilder key, final Object result) {
	if (this.formatterMap.containsKey(result.getClass())) {
	    return this.formatterMap.get(result.getClass()).parse(this.props.getProperty(key.toString()));
	} else {
	    final Field[] fields = result.getClass().getDeclaredFields();
	    for (final Field field : fields) {
		this.read(key, field, result);
	    }
	    return result;
	}
    }

    
    public void read(final StringBuilder key, final Field collection, final Object result) {
	final int initialLength = key.length();
	DefaultObjectParser.LOG.debug("Analysing object: " + result.getClass().getSimpleName() + " for field " + collection.getName());
	final String prefix = key.toString();
	if ((key.toString().length() != 0) && !StringUtils.endsWith(key.toString(), DefaultObjectParser.PROP_SEPA, false)) {
	    key.append(DefaultObjectParser.PROP_SEPA);
	}
	if (this.ignoreLoad(collection)) {
	    DefaultObjectParser.LOG.debug("Ignoring field " + collection.getName() + " in  object" + result.getClass().getSimpleName());
	} else {
	    DefaultObjectParser.LOG.debug("Analysing field :" + collection.getName());
	    key.setLength(prefix.length());
	    if (!prefix.equalsIgnoreCase(prefix + DefaultObjectParser.PROP_SEPA)) {
		key.append(result.getClass().getSimpleName());
		key.append(DefaultObjectParser.PROP_SEPA);
	    }
	    try {
		collection.setAccessible(true);
		key.append(collection.getName());
		DefaultObjectParser.LOG.debug("Expected result:" + collection.getType());
		this.finder.read(key, collection, result);

	    } catch (final IllegalArgumentException e) {
		DefaultObjectParser.LOG.error(e.getMessage());
	    } catch (final SecurityException e) {
		DefaultObjectParser.LOG.error(e.getMessage());
	    }
	}
	key.setLength(initialLength);

    }

    private boolean ignoreLoad(final Field field) {
	return (Modifier.isStatic(field.getModifiers())) || (Modifier.isTransient(field.getModifiers()));
    }

    public void readSimpleObject(final StringBuilder key, final Object result) {
	final String value = this.props.getProperty(key.toString());
	DefaultObjectParser.LOG.debug("Setting value by using key: " + key.toString() + " with value " + value);
	if (!StringUtils.isEmpty(value)) {
	    this.formatterMap.get(result.getClass()).parse(value, result);
	}

    }

}
